﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Windows;
using System.Reflection;
using System.Collections;
using PSE.Framework.Configuration;
using PSE.Framework.UI.Settings;
using System.IO;
using PSE.Framework.UI.Application.SmartClient.Configuration;

namespace PSE.Framework.UI.Application.SmartClient
{
    #region EventArgs

    public class LanguageChangedEventArgs : EventArgs
    {
        private string _language;

        public string Language
        {
            get { return _language; }
            set { _language = value; }
        }

        public LanguageChangedEventArgs(string language)
        {
            _language = language;
        }
    }

    public class ThemeChangedEventArgs : EventArgs
    {
        private string _theme;

        public string Theme
        {
            get { return _theme; }
            set { _theme = value; }
        }

        public ThemeChangedEventArgs(string theme)
        {
            _theme = theme;
        }
    }

    public class ZoomChangedEventArgs : EventArgs
    {
        private int _zoom;

        public int Zoom
        {
            get { return _zoom; }
            set { _zoom = value; }
        }

        public ZoomChangedEventArgs(int zoom)
        {
            _zoom = zoom;
        }
    }

    #endregion

    /// <summary>
    /// Classe para acesso e alteração das configurações da aplicação
    /// </summary>
    public abstract class ApplicationSettings<TConfigurationObjectType> : System.ComponentModel.ISupportInitializeNotification
        where TConfigurationObjectType : struct
    {
        #region Attributes

        private CurrentSettings _currentSettings;

        private string _defaultLanguage = null;
        private string _defaultTheme = null;

        private List<ThemeElement> _initialThemes = null;
        private List<ThemeElement> _installedThemes = null;
        private List<LanguageElement> _installedLanguages = null;

        private static ApplicationSettings<TConfigurationObjectType> _applicationSettings = null;

        public event EventHandler<ZoomChangedEventArgs> ZoomChanged;
        public event EventHandler<ThemeChangedEventArgs> ThemeChanged;
        public event EventHandler<LanguageChangedEventArgs> LanguageChanged;

        #endregion

        #region Properties

        /// <summary>
        /// Idioma default do sistema
        /// </summary>
        public string DefaultLanguage
        {
            get
            {
                return _defaultLanguage;
            }
        }

        /// <summary>
        /// Tema default do sistema
        /// </summary>
        public string DefaultTheme
        {
            get
            {
                return _defaultTheme;
            }
        }

        /// <summary>
        /// Idioma atual do sistema
        /// </summary>
        public string CurrentLanguage
        {
            get
            {
                return _currentSettings.CurrentLanguage;
            }
            protected set
            {
                if (_currentSettings.CurrentLanguage != value)
                {
                    _currentSettings.CurrentLanguage = value;
                    this.SaveSettings();
                }
            }
        }

        /// <summary>
        /// Tema atual do sistema
        /// </summary>
        public string CurrentTheme
        {
            get
            {
                return _currentSettings.CurrentTheme;
            }
            protected set
            {
                if (_currentSettings.CurrentTheme != value)
                {
                    _currentSettings.CurrentTheme = value;
                    this.SaveSettings();
                }
            }
        }

        /// <summary>
        /// Zoom atual do sistema
        /// </summary>
        public int CurrentZoom
        {
            get
            {
                return _currentSettings.CurrentZoom;
            }
            protected set
            {
                if (_currentSettings.CurrentZoom != value)
                {
                    _currentSettings.CurrentZoom = value;
                    this.SaveSettings();
                }
            }
        }
        
        /// <summary>
        /// Temas iniciais no sistema
        /// </summary>
        public ReadOnlyCollection<ThemeElement> InitialThemes
        {
            get
            {
                return _initialThemes.AsReadOnly();
            }
        }

        /// <summary>
        /// Temas instalados no sistema
        /// </summary>
        public ReadOnlyCollection<ThemeElement> InstalledThemes
        {
            get
            {
                return _installedThemes.AsReadOnly();
            }
        }

        /// <summary>
        /// Idiomas instalados no sistema
        /// </summary>
        public ReadOnlyCollection<LanguageElement> InstalledLanguages
        {
            get
            {
                return _installedLanguages.AsReadOnly();
            }
        }

        #endregion

        #region Constructor

        /// <summary>
        /// Construtor padrão
        /// </summary>
        protected ApplicationSettings()
        {
            _currentSettings = CurrentSettings.GetInstance();

            // Carregando idiomas
            LanguagesConfigurationSection languages = ConfigurationManager.GetSection("languages") as LanguagesConfigurationSection;
            _installedLanguages = new List<LanguageElement>();

            if (languages != null)
            {
                _defaultLanguage = languages.DefaultLanguage;

                foreach (LanguageElement language in languages.InstalledLanguages)
                {
                    _installedLanguages.Add(language);
                }
            }

            //Carregando temas iniciais
            // Carregando temas
            ThemesConfigurationSection themes = ConfigurationManager.GetSection("themes") as ThemesConfigurationSection;
            _initialThemes = new List<ThemeElement>();
            _installedThemes = new List<ThemeElement>();
            if (themes != null)
            {
                _defaultTheme = themes.DefaultTheme;

                foreach (ThemeElement initialtheme in themes.InitialThemes)
                {
                    _initialThemes.Add(initialtheme);
                }
                foreach (ThemeElement theme in themes.InstalledThemes)
                {
                    _installedThemes.Add(theme);
                }
            }
        }

        #endregion

        #region Methods

        protected abstract ApplicationConfigurationObject<TConfigurationObjectType> CreateApplicationConfigurationObject();

        /// <summary>
        /// Carrega as configurações salvas
        /// </summary>
        private void LoadLocalSettings()
        {
            IConfigurationSettings<TConfigurationObjectType> configurationSettings = ConfigurationSettingsManager<TConfigurationObjectType>.GetLocalConfigurationSettings();
            IConfigurationObject<TConfigurationObjectType> appConfig = this.CreateApplicationConfigurationObject();

            // Tentando carregar configurações salvas
            if (!configurationSettings.Load(ref appConfig))
            {
                // Não existem configurações salvas, inicializa valores default
                ((ApplicationConfigurationObject<TConfigurationObjectType>)appConfig).Language = this.DefaultLanguage;
                ((ApplicationConfigurationObject<TConfigurationObjectType>)appConfig).Theme = this.DefaultTheme;
                ((ApplicationConfigurationObject<TConfigurationObjectType>)appConfig).Zoom = 100;
            }

            // Preenchendo properties
            _currentSettings.CurrentLanguage = ((ApplicationConfigurationObject<TConfigurationObjectType>)appConfig).Language;
            _currentSettings.CurrentTheme = ((ApplicationConfigurationObject<TConfigurationObjectType>)appConfig).Theme;
            _currentSettings.CurrentZoom = ((ApplicationConfigurationObject<TConfigurationObjectType>)appConfig).Zoom;
        }

        /// <summary>
        /// Carrega as configurações salvas
        /// </summary>
        private void LoadServerSettings()
        {
            IConfigurationSettings<TConfigurationObjectType> configurationSettings = ConfigurationSettingsManager<TConfigurationObjectType>.GetServerConfigurationSettings();

            // Só recarrega settings se existir um provider remoto
            if (configurationSettings != null)
            {
                IConfigurationObject<TConfigurationObjectType> appConfig = this.CreateApplicationConfigurationObject();

                // Tentando carregar configurações salvas
                if (configurationSettings.Load(ref appConfig))
                {
                    // Se existirem configurações remotas salvas, preenche properties e salva uma cópia localmente
                    _currentSettings.CurrentLanguage = ((ApplicationConfigurationObject<TConfigurationObjectType>)appConfig).Language;
                    _currentSettings.CurrentTheme = ((ApplicationConfigurationObject<TConfigurationObjectType>)appConfig).Theme;
                    _currentSettings.CurrentZoom = ((ApplicationConfigurationObject<TConfigurationObjectType>)appConfig).Zoom;

                    ConfigurationSettingsManager<TConfigurationObjectType>.GetLocalConfigurationSettings().Save(appConfig);
                }
            }
        }

        /// <summary>
        /// Carrega o tema inicial
        /// </summary>
        /// <param name="theme">Thema a ser carregado</param>
        private void LoadInitialTheme(string theme)
        {
            if (string.IsNullOrEmpty(theme))
                throw new ArgumentNullException("theme");

            ThemeElement themeInfo = this.InitialThemes.ToList().Find(m => m.Name == theme);

            if (themeInfo == null)
                throw new ArgumentException(string.Format("The theme '{0}' is not installed", theme), "theme");

            try
            {
                LoadResouces(themeInfo.Assemblies);
            }
            catch (Exception ex)
            {
                throw new ConfigurationErrorsException(string.Format("Invalid configuration for theme '{0}'", theme), ex);
            }
        }

        /// <summary>
        /// Carrega o tema informado
        /// </summary>
        /// <param name="theme">Thema a ser carregado</param>
        private void LoadTheme(string theme)
        {
            if (string.IsNullOrEmpty(theme))
                throw new ArgumentNullException("theme");

            ThemeElement themeInfo = this.InstalledThemes.ToList().Find(m => m.Name == theme);

            if (themeInfo == null)
                throw new ArgumentException(string.Format("The theme '{0}' is not installed", theme), "theme");

            try
            {
                LoadResouces(themeInfo.Assemblies);
            }
            catch (Exception ex)
            {
                throw new ConfigurationErrorsException(string.Format("Invalid configuration for theme '{0}'", theme), ex);
            }
        }

        /// <summary>
        /// Carrega o idioma informado
        /// </summary>
        /// <param name="language">Idioma a ser carregado</param>
        private void LoadLanguage(string language)
        {
            if (string.IsNullOrEmpty(language))
                throw new ArgumentNullException("language");

            LanguageElement languageInfo = this.InstalledLanguages.ToList().Find(m => m.Name == language);

            if (languageInfo == null)
                throw new ArgumentException(string.Format("The language '{0}' is not installed", language), "language");

            try
            {
                LoadResouces(languageInfo.Assemblies);
            }
            catch (Exception ex)
            {
                throw new ConfigurationErrorsException(string.Format("Invalid configuration for language '{0}'", language), ex);
            }
        }

        /// <summary>
        /// Carrega o resource informado
        /// </summary>
        /// <param name="assemblies"></param>
        private void LoadResouces(ConfigurationCollection<ClassToInstanceInformation> assemblies)
        {
            foreach (ClassToInstanceInformation item in assemblies)
            {
                ResourceDictionary dictionary = GetResource(item);

                //se aplicacao ja esta aberta
                if (this.IsInitialized)
                    //Solução de contorno para o problema de memory leak relacionado a DynamicResource + ToolTip
                    WalkDictionary(dictionary);

                // Configurando dictionary de idiomas atual
                System.Windows.Application.Current.Resources.MergedDictionaries.Add(dictionary);

                //se aplicacao esta sendo aberta
                if (!this.IsInitialized)
                    //Solução de contorno para o problema de memory leak relacionado a DynamicResource + ToolTip
                    WalkDictionary(dictionary);
            }

        }

        /// <summary>
        /// Altera o idioma atual da aplicação
        /// </summary>
        /// <param name="language">Idioma a ser carregado</param>
        /// <returns>true se alterou com sucesso, false se idioma informado já está carregado</returns>
        public bool ChangeLanguage(string language)
        {
            if (string.IsNullOrEmpty(language))
                throw new ArgumentNullException("language");

            // Só troca se idioma for diferente do atual
            if (language != this.CurrentLanguage)
            {
                // Carregando idioma indicado
                this.LoadLanguage(language);

                // Salvando idioma atual
                this.CurrentLanguage = language;

                if (LanguageChanged != null)
                    LanguageChanged(this, new LanguageChangedEventArgs(language));

                return true;
            }

            return false;
        }

        /// <summary>
        /// Altera o tema atual da aplicação
        /// </summary>
        /// <param name="theme">Tema a ser carregado</param>
        /// <returns>true se alterou com sucesso, false se tema informado já está carregado</returns>
        public bool ChangeTheme(string theme)
        {
            if (string.IsNullOrEmpty(theme))
                throw new ArgumentNullException("theme");

            // Só troca se tema for diferente do atual
            if (theme != this.CurrentTheme)
            {
                // Carregando tema indicado
                this.LoadTheme(theme);

                // Salvando tema atual
                this.CurrentTheme = theme;

                if (ThemeChanged != null)
                    ThemeChanged(this, new ThemeChangedEventArgs(theme));

                return true;
            }

            return false;
        }

        /// <summary>
        /// Altera o zoom atual da aplicação
        /// </summary>
        /// <param name="zoomProportion">Proporção desejada de zoom a ser aplicada em toda aplicação</param>
        /// <returns>true se alterou com sucesso, false se zoom informado já está carregado</returns>
        public bool ChangeZoom(int zoomProportion)
        {
            // Só troca se idioma for diferente do atual
            if (zoomProportion != _currentSettings.CurrentZoom)
            {
                // Salva o zoom na instãncia de current settings
                //_currentSettings.CurrentZoom = zoomProportion;
                this.CurrentZoom = zoomProportion;

                if (ZoomChanged != null)
                    ZoomChanged(this, new ZoomChangedEventArgs(zoomProportion));

                return true;
            }

            return false;
        }

        /// <summary>
        /// Salva as configurações
        /// </summary>
        private void SaveSettings()
        {
            IConfigurationSettings<TConfigurationObjectType> configurationSettings = ConfigurationSettingsManager<TConfigurationObjectType>.GetConfigurationSettings();
            ApplicationConfigurationObject<TConfigurationObjectType> appConfig = this.CreateApplicationConfigurationObject();

            // Preenchendo objeto
            appConfig.Language = _currentSettings.CurrentLanguage;
            appConfig.Theme = _currentSettings.CurrentTheme;
            appConfig.Zoom = _currentSettings.CurrentZoom;

            // Tentando salvar configurações
            configurationSettings.Save((IConfigurationObject<TConfigurationObjectType>)appConfig);
        }

        /// <summary>
        /// GetResource
        /// </summary>
        /// <param name="instanceInfo"></param>
        /// <returns></returns>
        private ResourceDictionary GetResource(ClassToInstanceInformation instanceInfo)
        {
            Assembly resource = Assembly.Load(instanceInfo.Assembly);
            string nmspace = resource.GetName().Name;

            Uri dictionary = new Uri(nmspace + @";;;component\" + instanceInfo.Class, UriKind.Relative);

            return System.Windows.Application.LoadComponent(dictionary) as ResourceDictionary;
        }

        /// <summary>
        /// Método que varre os dicionário de recursos da aplicação realizando assim o primeiro acesso aos mesmos.
        /// Esse método teve de ser criado devido a um bug do WPF que gera um memory leak
        /// quando associado um DynamicResource em um ToolTip.
        /// Informação extráída do seguinte endereço:
        /// http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/b97a5f83-5394-430e-9a78-9d3a957e3537/
        /// </summary>
        /// <param name="resources"></param>
        private static void WalkDictionary(ResourceDictionary resources)
        {
            foreach (DictionaryEntry entry in resources) ;

            foreach (ResourceDictionary rd in resources.MergedDictionaries)
                WalkDictionary(rd);
        }

        #region ISupportInitialize Members

        /// <summary>
        /// Carrega os settings default da aplicação
        /// </summary>
        public void BeginInit()
        {
            if (this.IsInitialized)
                throw new InvalidOperationException("Unable to BeginInit, ApplicationSettings has already been initialized");

            // Carregando os settings locais
            this.LoadLocalSettings();

            // Carregando o idoma configurado localmente na aplicação
            this.LoadLanguage(this.CurrentLanguage);

            // Carregando o tema configurado localmente na aplicação
            this.LoadInitialTheme("Common");
            this.LoadTheme(this.CurrentTheme);
        }

        /// <summary>
        /// Carrega os settings específicos do usuário da aplicação
        /// </summary>
        public void EndInit()
        {
            if (this.IsInitialized)
                throw new InvalidOperationException("Unable to EndInit, ApplicationSettings has already been initialized");

            // Salva idioma atual
            string currentLanguage = this.CurrentLanguage;
            // Salva tema atual
            string currentTheme = this.CurrentTheme;

            // Recarregando os settings, para capturar os settings remotos (se existirem)
            this.LoadServerSettings();

            // Verifica se idioma mudou
            if (currentLanguage != this.CurrentLanguage)
            {
                // Se mudou, carrega idioma atual
                this.LoadLanguage(this.CurrentLanguage);
            }
            // Verifica se tema mudou
            if (currentTheme != this.CurrentTheme)
            {
                // Se mudou, carrega tema atual
                this.LoadTheme(this.CurrentTheme);
            }

            // Marcando settings como Initialized
            _isInitialized = true;
            // Disparando evento Initialized
            this.OnInitialized(new EventArgs());
        }

        #endregion

        #region ISupportInitializeNotification Members

        /// <summary>
        /// Evento disparado quando as configurações forem totalmente carregadas
        /// </summary>
        public event EventHandler Initialized;

        /// <summary>
        /// Dispara evento <c>Initialized</c>
        /// </summary>
        /// <param name="e">Parâmetros do evento</param>
        protected virtual void OnInitialized(EventArgs e)
        {
            if (Initialized != null)
            {
                Initialized(this, e);
            }
        }

        private bool _isInitialized = false;

        /// <summary>
        /// Indica que as configurações foram totalmente carregadas
        /// </summary>
        public bool IsInitialized
        {
            get { return _isInitialized; }
        }

        #endregion

        #endregion
    }
}
